Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement recursive plugin discovery #68811

Merged

Conversation

pgayvallet
Copy link
Contributor

@pgayvallet pgayvallet commented Jun 10, 2020

Summary

Fix #59189

Update core plugin discovery system and the kibana platform optimizer to look for plugin in sub directories.

Checklist

@pgayvallet pgayvallet added Feature:New Platform Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc v7.9.0 v8.0.0 labels Jun 10, 2020
...scanDirs.map((dir) => `${dir}/*/kibana.json`),
...scanDirs.map((dir) => `${dir}/**/kibana.json`),
Copy link
Contributor Author

@pgayvallet pgayvallet Jun 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparing this diff with the changes in plugins_discovery.ts made me cry a little.

Note that the optimizer does not exactly reproduce the logic used in the discovery system:

  • It doesn't handle maxDepth
  • It will discover plugins nested inside other plugins

However, I think this is acceptable. Worse case some plugins will be build that are not going to be discovered by core's discovery system. But that would be detected during the development phase anyway. (and implementing the exact same logic would cause findKibanaPlatformPlugins to become way more complex than its currently is)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder if we could have some sort of maxDepth by using a (very unreadable) glob like:

`${dir}/{*,*/*,*/*/*,*/*/*/*,*/*/*/*/*}/kibana.json`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either that or

 ...scanDirs
            .map((dir) => [
              `${dir}/*/kibana.json`,
              `${dir}/*/*/kibana.json`,
              `${dir}/*/*/*/kibana.json`,
              `${dir}/*/*/*/*/kibana.json`,
              `${dir}/*/*/*/*/*/kibana.json`,
            ])
            .flat(),

Which is a little longer (even if it can be extracted to a function), but probably more explicit.

Not 100% sure this is really needed (as it doesn't address the second point), but I have no problem implementing that if we think it's a little better can current implem.

wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's err on the side of caution here and go ahead and do that. Optimizer is already slow as it is, let's be defensive.

Comment on lines +109 to +114
// jest relies on the filesystem to get sourcemaps when using console.log
// which breaks with the mocked FS, see https://github.com/tschaub/mock-fs/issues/234
// hijacking logging to process.stdout as a workaround for this suite.
jest.spyOn(console, 'log').mockImplementation((...args) => {
process.stdout.write(args + '\n');
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self explicit comment. TIL, jest patches console.log to add source-map capabilities to the output. But it uses fs.lstat at some point in the process, and we are mocking the whole FS for this suite.

There is an open issue to implements layering to mock-fs, but until then, the process.stdout seems like an acceptable workaround. (This doesn't affect the testing behavior in any way)

Comment on lines 126 to 136
mockFs(
{
[KIBANA_ROOT]: {
src: {
plugins: {
plugin_a: Plugins.valid('pluginA'),
},
},
plugins: {
plugin_b: Plugins.valid('pluginB'),
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why mock-fs:

  • Better test data isolation. The mocked FS is described in each test, which makes it way easier to understand what we are doing that having to see the content of a fixture folder (at least ihmo)
  • ATM, we don't have any tool to mock the Env class, and the pluginSearchPaths are hardcoded in it's constructor, meaning that discover will necessarily scan {cwd}/src/plugins. We can't have it use another root folder without dirty tricks. mock-fs allow to easily mock the content of the kibana directory
  • More powerful than persistent test folders committed in the repository (can define permissions on a file/folder in the fs definition for example. Doing the same on a real FS always leads to troubles)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it's a bit easier to grok this if we used regular directory paths as keys rather than nested objects (which mock-fs supports):

mockFs({
  [KIBANA_ROOT]: {
    'src/plugins/plugin_a': Plugins.valid('pluginA'),
    'plugins/plugin_b': /** */,
     'x-pack/plugins/plugin_c': /** */,
  }
})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's supposed to, however I'm having failures when running your changes.

When looking at the actual implementation of mock-fs's filesystem, it seems the populate logic does not accept nested multi-path keys (...)

This works:

    mockFs(
      {
        [`${KIBANA_ROOT}/src/plugins/plugin_a`]: Plugins.valid('pluginA'),
        [`${KIBANA_ROOT}/plugins/plugin_b`]: Plugins.valid('pluginB'),
      },
      { createCwd: false }
    );

but this doesn't:

mockFs(
      {
        [KIBANA_ROOT]: {
          'src/plugins/plugin_a': Plugins.valid('pluginA'),
          'plugins/plugin_b': Plugins.valid('pluginB'),
        },
      },
      { createCwd: false }
    );

wdyt? Do we still prefer the long

[`${KIBANA_ROOT}/src/plugins/plugin_a`]: Plugins.valid('pluginA'),

syntax over the one currently used?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that would be slight more clear, yes.

@pgayvallet pgayvallet marked this pull request as ready for review June 11, 2020 08:00
@pgayvallet pgayvallet requested review from a team as code owners June 11, 2020 08:00
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-platform (Team:Platform)

@pgayvallet pgayvallet added the release_note:skip Skip the PR/issue when compiling release notes label Jun 11, 2020
@spalger
Copy link
Contributor

spalger commented Jun 11, 2020

@pgayvallet Out of curiosity, as described by #59189 this doesn't support plugins inside of plugins right? (kind of implied by "recursive plugin discovery")

@pgayvallet
Copy link
Contributor Author

this doesn't support plugins inside of plugins right?

Right. The PR title might be a little misleading. It scans sub-directories until it finds a plugin, and then won't go further on this specific branch.

(Even if this specific rule is not 'respected' by the way i modified the optimizer, see #68811 (comment))

Copy link
Contributor

@spalger spalger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kbn/optimizer changes LGTM

Copy link
Contributor

@joshdover joshdover left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested locally without any issues. A few comments, but no major problems stood out to me.

...scanDirs.map((dir) => `${dir}/*/kibana.json`),
...scanDirs.map((dir) => `${dir}/**/kibana.json`),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder if we could have some sort of maxDepth by using a (very unreadable) glob like:

`${dir}/{*,*/*,*/*/*,*/*/*/*,*/*/*/*/*}/kibana.json`

Comment on lines 126 to 136
mockFs(
{
[KIBANA_ROOT]: {
src: {
plugins: {
plugin_a: Plugins.valid('pluginA'),
},
},
plugins: {
plugin_b: Plugins.valid('pluginB'),
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it's a bit easier to grok this if we used regular directory paths as keys rather than nested objects (which mock-fs supports):

mockFs({
  [KIBANA_ROOT]: {
    'src/plugins/plugin_a': Plugins.valid('pluginA'),
    'plugins/plugin_b': /** */,
     'x-pack/plugins/plugin_c': /** */,
  }
})


await expect(
error$
it('return errors when the plugin search path is not accessible', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a similar test for if the kibana.json file is inaccessible?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added. However even in 000 mode, the stat operation returns in success (which is normal). The error is therefor catched a little late, in parseManifest. It's probably still acceptable (and I don't really want to perform a try-read just to catch the error sooner)

const manifestPath = (...pluginPath: string[]) =>
resolve(KIBANA_ROOT, 'src', 'plugins', ...pluginPath, 'kibana.json');

describe('plugins discovery system', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a test that uses symlinks? I think that's probably a common use-case for 3rd party plugins. Good news is it looks like mock-fs supports symlinks.

Copy link
Contributor Author

@pgayvallet pgayvallet Jun 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you have in mind exactly? A symlink from a nested plugin directory to an arbitrary other path on the FS containing the plugin files?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main use case doesn't really have to do with nested directories, but I think it's probably common for deployments to symlink the <kibana root>/plugins directory for custom plugins.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added test

@joshdover joshdover self-requested a review June 25, 2020 13:23
Copy link
Contributor

@joshdover joshdover left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM after #68811 (comment) and #68811 (comment) are resolved. Lots of interest in this change, it's going be very helpful!

@pgayvallet
Copy link
Contributor Author

retest

@kibanamachine
Copy link
Contributor

💚 Build Succeeded

Build metrics

✅ unchanged

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@pgayvallet pgayvallet merged commit 8485d2f into elastic:master Jun 30, 2020
pgayvallet added a commit to pgayvallet/kibana that referenced this pull request Jun 30, 2020
* implements recursive scanning in plugin discovery system

* update optimizer to find plugins in sub-directories

* update renovate

* update optimizer IT snapshot

* refactor processPluginSearchPaths$ and add test for inaccessible manifest

* add symlink test

* add maxDepth to the optimizer

* adapt mockFs definitions

* remove `flat` usage
# Conflicts:
#	renovate.json5
pgayvallet added a commit that referenced this pull request Jun 30, 2020
* implements recursive scanning in plugin discovery system

* update optimizer to find plugins in sub-directories

* update renovate

* update optimizer IT snapshot

* refactor processPluginSearchPaths$ and add test for inaccessible manifest

* add symlink test

* add maxDepth to the optimizer

* adapt mockFs definitions

* remove `flat` usage
# Conflicts:
#	renovate.json5
jloleysens added a commit to jloleysens/kibana that referenced this pull request Jun 30, 2020
…ata-streams

* 'master' of github.com:elastic/kibana: (50 commits)
  [Logs UI] [Alerting] "Group by" functionality (elastic#68250)
  [Discover] Deangularize Skip to bottom button (elastic#69811)
  Implement recursive plugin discovery (elastic#68811)
  Use ts-expect-error in platform code (elastic#69883)
  [SIEM][Detection Engine][Lists] Moves getQueryFilter to common folder for use by both front and backend
  [Ingest Manager][SECURITY SOLUTION] adjust config reassign link and add roundtrip to Reassignment flow (elastic#70208)
  [Security][Lists] Add API functions and react hooks for value list APIs (elastic#69603)
  [ILM] Fix bug when clearing priority field (elastic#70154)
  [Platform][Security] Updates cluster_manager ignorePaths to include security scripts (elastic#70139)
  [IngestManager] Allow to filter agent by packages (elastic#69731)
  [code coverage] exclude folders: test_helpers, tests_bundle (elastic#70199)
  [Metrics UI] UX improvements for saved views (elastic#69910)
  [APM] docs: unique transaction troubleshooting (elastic#69831)
  Cross cluster search functional test with minimun privileges assigned to the test_user (elastic#70007)
  [Maps] choropleth layer wizard (elastic#69699)
  Make custom errors by extending Error (elastic#69966)
  [Ingest Manager] Support updated package output structure (elastic#69864)
  Resolver test coverage (elastic#70246)
  Async Discover search test (elastic#64388)
  [ui-shared-deps] include styled-components (elastic#69322)
  ...

# Conflicts:
#	x-pack/plugins/snapshot_restore/server/types.ts
gmmorris added a commit to gmmorris/kibana that referenced this pull request Jun 30, 2020
…bana into alerting/consumer-based-rbac

* 'alerting/consumer-based-rbac' of github.com:gmmorris/kibana: (49 commits)
  [Discover] Deangularize Skip to bottom button (elastic#69811)
  Implement recursive plugin discovery (elastic#68811)
  Use ts-expect-error in platform code (elastic#69883)
  [SIEM][Detection Engine][Lists] Moves getQueryFilter to common folder for use by both front and backend
  [Ingest Manager][SECURITY SOLUTION] adjust config reassign link and add roundtrip to Reassignment flow (elastic#70208)
  [Security][Lists] Add API functions and react hooks for value list APIs (elastic#69603)
  [ILM] Fix bug when clearing priority field (elastic#70154)
  [Platform][Security] Updates cluster_manager ignorePaths to include security scripts (elastic#70139)
  [IngestManager] Allow to filter agent by packages (elastic#69731)
  [code coverage] exclude folders: test_helpers, tests_bundle (elastic#70199)
  [Metrics UI] UX improvements for saved views (elastic#69910)
  [APM] docs: unique transaction troubleshooting (elastic#69831)
  Cross cluster search functional test with minimun privileges assigned to the test_user (elastic#70007)
  [Maps] choropleth layer wizard (elastic#69699)
  Make custom errors by extending Error (elastic#69966)
  [Ingest Manager] Support updated package output structure (elastic#69864)
  Resolver test coverage (elastic#70246)
  Async Discover search test (elastic#64388)
  [ui-shared-deps] include styled-components (elastic#69322)
  SECURITY-ENDPOINT: add host properties (elastic#70238)
  ...
Bamieh pushed a commit to Bamieh/kibana that referenced this pull request Jul 1, 2020
* implements recursive scanning in plugin discovery system

* update optimizer to find plugins in sub-directories

* update renovate

* update optimizer IT snapshot

* refactor processPluginSearchPaths$ and add test for inaccessible manifest

* add symlink test

* add maxDepth to the optimizer

* adapt mockFs definitions

* remove `flat` usage
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:New Platform release_note:skip Skip the PR/issue when compiling release notes Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc v7.9.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support plugin discovery in sub-directories
5 participants